package com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.properties;

import com.intellij.openapi.application.ReadAction;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiTypes;
import com.intellij.ui.SimpleListCellRenderer;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.mxgraph.swing.RadDecisionComponent;
import com.mxgraph.swing.RadSwitchTaskComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.FormEditingUtil;
import com.neuronbit.businessflow.intellij.editor.swing.editor.RadComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.RadJavaMethodComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.InplaceContext;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.Property;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.PropertyEditor;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.PropertyRenderer;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.editors.ComboBoxPropertyEditor;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.renderers.LabelPropertyRenderer;
import com.neuronbit.businessflow.intellij.model.FormattedMethodString;
import com.neuronbit.businessflow.intellij.util.PsiUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;

/**
 * The type Method to bind property.
 */
public class CallableMethodToBindProperty extends Property<RadJavaMethodComponent, String> {
    private LabelPropertyRenderer<String> myRenderer;
    private ComboBoxPropertyEditor<String> myEditor;

    /**
     * Instantiates a new Method to bind property.
     *
     * @param parent the parent
     * @param name   the name
     */
    public CallableMethodToBindProperty(Property parent, @NotNull String name) {
        super(parent, name);
    }

    /**
     * Instantiates a new Method to bind property.
     *
     * @param name the name
     */
    public CallableMethodToBindProperty(@NotNull String name) {
        super(null, name);
    }

//    @Override
//    public Property @NotNull [] getChildren(RadJavaMethodComponent component) {
//        return new Property[]{
//                new MethodParamsProperty(this, "parameters"),
//                new ClassToBindProperty3(this, component.getProject(), "return")
//        };
//    }

    @Override
    public String getValue(RadJavaMethodComponent component) {
        FormattedMethodString methodToBind = component.getMethodToBind();
        if (methodToBind != null) {
            return methodToBind.getValue();
        } else {
            return null;
        }
    }

    @Override
    protected void setValueImpl(RadJavaMethodComponent component, String value) throws Exception {
        component.setMethodToBind(value);
    }

    @Override
    public @NotNull PropertyRenderer<String> getRenderer() {
        if (myRenderer == null) {
            myRenderer = new LabelPropertyRenderer<>() {
                @Override
                protected void customize(@NotNull String value) {
                    setText(value);
                }
            };
        }
        return myRenderer;
    }

    @Override
    public @Nullable PropertyEditor<String> getEditor() {
        if (myEditor == null) {
            myEditor = new MethodComboBoxPropertyEditor(getParent());
        }
        return myEditor;
    }

    private static class MethodComboBoxPropertyEditor extends ComboBoxPropertyEditor<String> {
        private final Property parent;
        private static final Set<String> excludeSet = new HashSet<>();

        static {
            excludeSet.add("Object");
            excludeSet.add("registerNatives");
            excludeSet.add("getClass");
            excludeSet.add("hashCode");
            excludeSet.add("equals");
            excludeSet.add("clone");
            excludeSet.add("toString");
            excludeSet.add("notify");
            excludeSet.add("notifyAll");
            excludeSet.add("wait");
            excludeSet.add("finalize");
        }

        /**
         * Instantiates a new Method combo box property editor.
         *
         * @param parent the parent
         */
        public MethodComboBoxPropertyEditor(@Nullable Property parent) {
            this.parent = parent;
            myCbx.setModel(new DefaultComboBoxModel<>());
            myCbx.setRenderer(SimpleListCellRenderer.create("NONE", s -> s));
        }

        @Override
        public JComponent getComponent(RadComponent component, String value, InplaceContext inplaceContext) {
            myCbx.setSelectedItem(value);
            String className = ((RadJavaMethodComponent) component).getClassToBind();
            if (className != null) {
                final PsiClass aClass;
                try {
                    aClass = ReadAction.nonBlocking(() -> FormEditingUtil.findClassToBind(component.getModule(), className.toString())).submit(
                            AppExecutorUtil.getAppExecutorService()).get();

                    if (aClass == null) {
                        return myCbx;
                    }

                    String[] methods = ReadAction.nonBlocking(() -> Arrays.stream(aClass.getMethods()).filter(psiMethod -> {
                                if (component instanceof RadSwitchTaskComponent) {
                                    return psiMethod.getReturnType() != null && PsiUtils.isEnumValue(psiMethod.getReturnType());
                                }
                                if (component instanceof RadDecisionComponent) {
                                    return psiMethod.getReturnType() != null && psiMethod.getReturnType().equals(PsiTypes.booleanType());
                                }
                                return !excludeSet.contains(psiMethod.getName());
                            })
                            .map(PsiUtils::formatMethod)
                            .toArray(String[]::new)).submit(
                            AppExecutorUtil.getAppExecutorService()).get();
                    myCbx.setModel(new DefaultComboBoxModel<>(methods));
                } catch (InterruptedException | ExecutionException e) {
                    throw new RuntimeException(e);
                }
            }

            return myCbx;
        }
    }
}
